/*
Created by yangyiwen 2020/02/02  base on mapview or sceneveiwer
*/
define([
    'dojo/_base/declare',
    "esri/views/3d/externalRenderers",
    "esri/geometry/SpatialReference",
    "esri/layers/SceneLayer",
    "dojo/on",
    "dojo/domReady!"
],
    function (declare,externalRenderers, SpatialReference, SceneLayer, on) {
        var VideoFusion = declare(null, {
            constructor: function (options, view,callback) {
                this.options = options;
                this.fusionExternalRenderer = null;
                this.gb = null;
                this.callback = callback
                this.view = view;
                this.init();
            },
            init: function () {
                this.createVideoRenderer()
            },
            createVideoRenderer: function () {
                var gv = this.view;
                var options =this.options;
                this.fusionExternalRenderer = {
                    globalThis: this,
                    renderer: null, // three.js renderer
                    camera: null, // three.js camera
                    scene: null, // three.js scene

                    ambient: null, // three.js ambient light source

                    iss: null, // ISS model
                    issGroup: null,

                    frustumHelper: null,
                    frustPointsList: [],
                    fusionCameraConfig: {
                        calibResult: {
                            "info": options.videoconfig["info"],
                            "meanError": options.videoconfig["meanError"],
                            "Intrinsics": options.videoconfig["Intrinsics"],
                            "distCoeffs": options.videoconfig["distCoeffs"],
                            "imageWidth": options.videoconfig["imageWidth"],
                            "imageHeight": options.videoconfig["imageHeight"],
                            "isUseFishEyeModel": options.videoconfig["isUseFishEyeModel"],
                            "images": options.videoconfig["images"],
                      }
                    },

                    generateFrustumHelper: function (fov, ratio, plane, position, pose) {
                        var frustFOV = fov;
                        var frustRatio = ratio;
                        var frustPlane = plane;
                        var frustH =
                            frustPlane * Math.tan(THREE.Math.degToRad(frustFOV * 0.5));
                        var frustW = frustH * frustRatio;
                        this.frustPointsList = [
                            new THREE.Vector3(),
                            new THREE.Vector3(-frustW, -frustH, frustPlane),
                            new THREE.Vector3(frustW, -frustH, frustPlane),
                            new THREE.Vector3(-frustW, frustH, frustPlane),
                            new THREE.Vector3(frustW, frustH, frustPlane)
                        ];
                        var frustGeom = new THREE.BufferGeometry().setFromPoints(
                            this.frustPointsList
                        );
                        frustGeom.setIndex([
                            0,
                            1,
                            0,
                            2,
                            0,
                            3,
                            0,
                            4,
                            1,
                            2,
                            2,
                            4,
                            4,
                            3,
                            3,
                            1
                        ]);
                        var frustumHelperInFunc = new THREE.LineSegments(
                            frustGeom,
                            new THREE.LineBasicMaterial({
                                color: 0xff0000,
                                // colorWrite: false,
                                // opacity: 0.0,
                                transparent: true
                            })
                        );
                        frustumHelperInFunc.position.set(
                            parseFloat(position[0]),
                            parseFloat(position[1]),
                            parseFloat(position[2])
                        );
                        // frustumHelper.up.set(-0.0153, 0.1167, 0.9930);
                        frustumHelperInFunc.setRotationFromMatrix(pose);
                        frustumHelperInFunc.updateMatrixWorld();
                        return frustumHelperInFunc;
                    },

                    applyCameraTexture: function () {
                        var result = this.fusionCameraConfig.calibResult;
                        var fovF =
                            (2 *
                                Math.atan(
                                    parseFloat(result.Intrinsics[1][2]) /
                                    parseFloat(result.Intrinsics[1][1])
                                ) *
                                180) /
                            Math.PI;
                        var poseAS = result.images[0]["Pose, camera to model"];
                        var poseMat4 = new THREE.Matrix4().set(
                            poseAS[0][0],
                            poseAS[0][1],
                            poseAS[0][2],
                            poseAS[0][3],
                            poseAS[1][0],
                            poseAS[1][1],
                            poseAS[1][2],
                            poseAS[1][3],
                            poseAS[2][0],
                            poseAS[2][1],
                            poseAS[2][2],
                            poseAS[2][3],
                            0.0,
                            0.0,
                            0.0,
                            1.0
                        );
                        this.frustumHelper = this.generateFrustumHelper(
                            fovF,
                            16 / 9,
                            20,
                            [poseAS[0][3], poseAS[1][3], poseAS[2][3]],
                            poseMat4
                        );
                        this.issGroup.add(this.frustumHelper);
                        this.frustumHelper.updateMatrixWorld();
                        let modelViewMatrixToFrustum = new THREE.Matrix4()
                            .getInverse(this.frustumHelper.matrixWorld)
                            .multiply(this.iss.matrixWorld.clone());
                        var distCoeffs = result["distCoeffs"];
                        let _this = this;
                        // console.log(_this);
                        let player = document.getElementById("flv-video");
                        let videoTexture = new THREE.VideoTexture(
                            document.getElementById("flv-video")
                        );
                        this.iss.traverse(function (child) {
                            if (child instanceof THREE.Mesh) {
                                _this.frustPointsList.forEach((p, idx) => {
                                    // console.log(
                                    //     _this.frustumHelper.localToWorld(new THREE.Vector3(0, 0, 0))
                                    // );
                                    // _this.frustumHelper.localToWorld(child.material.uniforms.frustum.value[idx].copy(p));
                                    child.material.uniforms.frustum.value[idx].copy(p);
                                });

                                if (player.buffered.length) {
                                    // debugger
                                    child.material.uniforms.cameraTexture.value = videoTexture;
                                } else {
                                    child.material.uniforms.cameraTexture.value = new THREE.TextureLoader().load(
                                        options.url + "/camera.png"
                                    );
                                }
                                child.material.uniforms.modelViewMatrixToFrustum.value = modelViewMatrixToFrustum.clone();
                                if (distCoeffs.length >= 5) {
                                    child.material.uniforms.distCoeff.value = new THREE.Vector4(
                                        distCoeffs[0],
                                        distCoeffs[1],
                                        distCoeffs[4],
                                        0
                                    );
                                } else {
                                    child.material.uniforms.distCoeff.value = new THREE.Vector4(
                                        distCoeffs[0],
                                        distCoeffs[1],
                                        distCoeffs[2],
                                        distCoeffs[3]
                                    );
                                }
                                child.material.uniforms.isFishEyeModel.value = false;
                                child.material.uniforms.useUndistort.value = true;
                                child.material.uniforms.isPreview.value = 1;
                                // _this.renderer.render(_this.scene, _this.camera);
                                child.material.needsUpdate = true;
                            }
                        });

                        this.frustPointsList.forEach((p, idx) => {
                            this.planeFusionMtl.uniforms.frustum.value[idx].copy(p);
                        });
                        if (player.buffered.length) {
                            this.planeFusionMtl.uniforms.cameraTexture.value = videoTexture;
                        } else {
                            this.planeFusionMtl.uniforms.cameraTexture.value = new THREE.TextureLoader().load(
                                options.url +"/camera.png"
                            );
                        }
                        this.planeFusionMtl.uniforms.modelViewMatrixToFrustum.value = modelViewMatrixToFrustum.clone();
                        if (distCoeffs.length >= 5) {
                            this.planeFusionMtl.uniforms.distCoeff.value = new THREE.Vector4(
                                distCoeffs[0],
                                distCoeffs[1],
                                distCoeffs[4],
                                0
                            );
                        } else {
                            this.planeFusionMtl.uniforms.distCoeff.value = new THREE.Vector4(
                                distCoeffs[0],
                                distCoeffs[1],
                                distCoeffs[2],
                                distCoeffs[3]
                            );
                        }
                        this.planeFusionMtl.uniforms.isFishEyeModel.value = false;
                        this.planeFusionMtl.uniforms.useUndistort.value = true;
                        this.planeFusionMtl.uniforms.isPreview.value = 1;
                        // _this.renderer.render(_this.scene, _this.camera);
                        this.planeFusionMtl.needsUpdate = true;
                    },

                    loadMesh: function () {
                      //CHY_reduced
                        var issMeshUrl = options.url +options.modelName+".obj";
                        let _this = this;
                        var loader = new THREE.OBJLoader(THREE.DefaultLoadingManager);
                        loader.load(
                            issMeshUrl,
                            function (object3d) {
                                this.iss = object3d;
                                this.iss.traverse(
                                    function (child) {
                                        if (child instanceof THREE.Mesh) {
                                            child.material = this.fusionMtl;
                                        }
                                    }.bind(this)
                                );

                                // add the model
                                this.issGroup.add(this.iss);

                                var renderPos = [0, 0, 0];
                                externalRenderers.toRenderCoordinates(
                                    gv,
                                    [0, 0, 0],
                                    0,
                                    SpatialReference.WGS84,
                                    renderPos,
                                    0,
                                    1
                                );
                                this.issGroup.position.set(
                                    renderPos[0],
                                    renderPos[1],
                                    renderPos[2]
                                );

                                this.issGroup.updateMatrix();
                                this.issGroup.updateMatrixWorld(true);
                                this.issGroup.updateWorldMatrix(true, true);
                                // console.log(this.issGroup);
                                // console.log(
                                //     this.issGroup.localToWorld(new THREE.Vector3(0, 0, 0))
                                // );

                                this.applyCameraTexture();

                                let planeGeo = new THREE.PlaneGeometry(30000, 30000);

                                this.plane = new THREE.Mesh(planeGeo, this.planeFusionMtl);
                                this.plane.position.set(0.0, 0.0, 1.0);
                                this.issGroup.add(this.plane);

                                // console.log(this.frustumHelper);
                            }.bind(this),
                            undefined,
                            function (error) {
                                // console.error("Error loading ISS mesh. ", error);
                            }
                        );
                    },

                    /**
                     * Setup function, called once by the ArcGIS JS API.
                     */
                    setup: function (context) {
                        gb = this;
                        // initialize the three.js renderer
                        //////////////////////////////////////////////////////////////////////////////////////
                        this.renderer = new THREE.WebGLRenderer({
                            context: context.gl,
                            premultipliedAlpha: false
                            // antialias: true,
                            // logarithmicDepthBuffer: true
                        });
                        this.renderer.setPixelRatio(window.devicePixelRatio);
                        this.renderer.setViewport(0, 0, gv.width, gv.height);

                        // prevent three.js from clearing the buffers provided by the ArcGIS JS API.
                        this.renderer.autoClearDepth = false;
                        this.renderer.autoClearStencil = false;
                        this.renderer.autoClearColor = false;
                        this.issGroup = new THREE.Group();

                        // The ArcGIS JS API renders to custom offscreen buffers, and not to the default framebuffers.
                        // We have to inject this bit of code into the three.js runtime in order for it to bind those
                        // buffers instead of the default ones.
                        var originalSetRenderTarget = this.renderer.setRenderTarget.bind(
                            this.renderer
                        );
                        this.renderer.setRenderTarget = function (target) {
                            originalSetRenderTarget(target);
                            if (target == null) {
                                context.bindRenderTarget();
                            }
                        };

                        // setup the three.js scene
                        ///////////////////////////////////////////////////////////////////////////////////////

                        this.scene = new THREE.Scene();

                        // setup the camera
                        this.camera = new THREE.PerspectiveCamera();

                        var cubeGeo = new THREE.BoxGeometry(100000, 100000, 100000);
                        var cubeMtl = new THREE.MeshBasicMaterial({
                            color: 0x00ff00,
                            wireframe: true
                        });
                        this.cubeMono = new THREE.Mesh(cubeGeo, cubeMtl);
                        this.scene.add(this.cubeMono);

                        // setup the shader material
                        this.fusionMtl = new THREE.ShaderMaterial({
                            uniforms: THREE.UniformsUtils.merge([
                                THREE.UniformsLib["common"],
                                THREE.UniformsLib.specularmap,
                                THREE.UniformsLib.envmap,
                                THREE.UniformsLib.aomap,
                                THREE.UniformsLib.lightmap,
                                THREE.UniformsLib.fog,
                                {
                                    hasVertexColor: { value: false },
                                    isFishEyeModel: { value: false },
                                    useUndistort: { value: true },
                                    baseColor: { value: new THREE.Color(0xffffff) },
                                    isPreview: { value: 0 },
                                    distCoeff: { value: new THREE.Vector4(0, 0, 0, 0) },
                                    frustum: {
                                        value: [
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3()
                                        ]
                                    },
                                    hasBaseTexture: {
                                        value: false
                                    },
                                    baseTexture: {
                                        value: null
                                    },
                                    cameraTexture: {
                                        value: new THREE.TextureLoader().load(options.url + "/camera.png")
                                    },
                                    modelViewMatrixToFrustum: {
                                        value: new THREE.Matrix4()
                                    },
                                    isTransparent: {
                                        value: true
                                    }
                                    // depthTexture: {
                                    //     value: depthRenderTarget.texture,
                                    // },
                                    // depthCameraNear: {
                                    //     value: depthCamera.near,
                                    // },
                                    // depthCameraFar: {
                                    //     value: depthCamera.far,
                                    // }
                                }
                            ]),
                            vertexColors: THREE.VertexColors,
                            depthTest: true,
                            transparent: true,

                            // wireframe: true,
                            // vertexShader: document.getElementById( 'vertexShader' ).textContent,
                            vertexShader: document.getElementById("vertexShader").textContent,
                            // fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
                            fragmentShader: document.getElementById("fragmentShader")
                                .textContent
                        });

                        this.planeFusionMtl = new THREE.ShaderMaterial({
                            uniforms: THREE.UniformsUtils.merge([
                                THREE.UniformsLib["common"],
                                THREE.UniformsLib.specularmap,
                                THREE.UniformsLib.envmap,
                                THREE.UniformsLib.aomap,
                                THREE.UniformsLib.lightmap,
                                THREE.UniformsLib.fog,
                                {
                                    hasVertexColor: { value: false },
                                    isFishEyeModel: { value: false },
                                    useUndistort: { value: true },
                                    baseColor: { value: new THREE.Color(0xffffff) },
                                    isPreview: { value: 0 },
                                    distCoeff: { value: new THREE.Vector4(0, 0, 0, 0) },
                                    frustum: {
                                        value: [
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3(),
                                            new THREE.Vector3()
                                        ]
                                    },
                                    hasBaseTexture: {
                                        value: false
                                    },
                                    baseTexture: {
                                        value: null
                                    },
                                    cameraTexture: {
                                        value: new THREE.TextureLoader().load(options.url + "/camera.png")
                                    },
                                    modelViewMatrixToFrustum: {
                                        value: new THREE.Matrix4()
                                    },
                                    isTransparent: {
                                        value: true
                                    }
                                    // depthTexture: {
                                    //     value: depthRenderTarget.texture,
                                    // },
                                    // depthCameraNear: {
                                    //     value: depthCamera.near,
                                    // },
                                    // depthCameraFar: {
                                    //     value: depthCamera.far,
                                    // }
                                }
                            ]),
                            vertexColors: THREE.VertexColors,
                            depthTest: true,
                            transparent: true,

                            // wireframe: true,
                            vertexShader: document.getElementById("vertexShader").textContent,
                            //   vertexShader:window.vertexShader,
                            // fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
                            fragmentShader: document.getElementById("fragmentShader")
                                .textContent
                        });

                        // setup scene lighting
                        this.ambient = new THREE.AmbientLight(0xffffff, 2);
                        this.scene.add(this.ambient);
                        this.scene.add(this.issGroup);
                        this.loadMesh();
                        context.resetWebGLState();
                    },

                    render: function (context) {
                        // update camera parameters
                        ///////////////////////////////////////////////////////////////////////////////////
                        var cam = context.camera;

                        this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
                        this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
                        this.camera.lookAt(
                            new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])
                        );

                        // Projection matrix can be copied directly
                        this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
                        // draw the scene
                        /////////////////////////////////////////////////////////////////////////////////////////////////////
                        this.renderer.state.reset();
                        this.renderer.render(this.scene, this.camera);

                        // // as we want to smoothly animate the ISS movement, immediately request a re-render
                        externalRenderers.requestRender(gv);

                        // cleanup
                        context.resetWebGLState();
                    },

                    lastPosition: null,
                    lastTime: null
                };
                window.videoRenders = this.fusionExternalRenderer;
                externalRenderers.add(gv, this.fusionExternalRenderer);
                this.callback()
            },

            closeVideo: function () {
                var globalview = this.view;
                if (this.fusionExternalRenderer) {
                    externalRenderers.remove(globalview, this.fusionExternalRenderer);
                    this.fusionExternalRenderer = null;
                }
            }
        })
        return VideoFusion;
    });
